{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "import datetime\n",
    "import random"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "random.choices(population, weights=None, cum_weights=None, k=1)、\n",
    "\n",
    "- population 表示要进行选择的序列；\n",
    "- weights 是可选参数，表示在选择时考虑的每个元素的权重；\n",
    "- cum_weights 也是可选参数，表示在选择时考虑的累积权重；\n",
    "- k 表示要选择的元素个数，默认值为 1。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'是'"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 因为车架号是五菱车的唯一标识,所以可以把这些唯一的数据放在一个列表 里\n",
    "# 通过随机获取列表中的车架号，从而达到模拟一辆车的多条数据，当我们需要的时候从中获取即可\n",
    "# 选择用集合，因为在python的集合中是不允许重复元素存在的\n",
    "vin_set = set()\n",
    "for i in range(20):\n",
    "    # 用random.choices用于在给定的序列中随机地选择一个或多个元素。\n",
    "    vin = ''.join(random.choices('0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ', k=17))\n",
    "    if vin not in vin_set:\n",
    "        vin_set.add(vin)\n",
    "# print(vin_set)\n",
    "# print(len(vin_set))\n",
    "\n",
    "\n",
    "# 行驶总里程\n",
    "# random.uniform用于生成指定范围内的随机浮点数。\n",
    "mileage = round(random.uniform(200, 50000), 2)\n",
    "\n",
    "# 车速(考虑到有些人可能会超速行驶,所以最高车速为)\n",
    "speed = round(random.uniform(0, 130), 2)\n",
    "\n",
    "# 车辆状态\n",
    "# 定义车辆状态\n",
    "vehicle_status = ['行驶中', '停车中', '故障']\n",
    "status = random.choices(vehicle_status,weights=[0.45,0.45,0.1])[0]\n",
    "status\n",
    "\n",
    "# 充电状态\n",
    "# 定义充电状态\n",
    "charging_status = ['未充电', '正在充电']\n",
    "charging = random.choice(charging_status)\n",
    "charging\n",
    "\n",
    "# 剩余电量SOC(10-100)\n",
    "soc = round(random.uniform(10, 100), 2)\n",
    "soc\n",
    "\n",
    "# SOC低报警\n",
    "soc_low_alarm = '是' if soc < 30 else '否'\n",
    "soc_low_alarm\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "随机时间对象是： 2023-05-23 11:17:03\n"
     ]
    }
   ],
   "source": [
    "import datetime\n",
    "import random\n",
    "\n",
    "# 获取当前日期\n",
    "now = datetime.datetime.now()\n",
    "\n",
    "# 生成随机的小时数、分钟数和秒数，并构造时间对象\n",
    "random_time = datetime.time(random.randint(0, 23), random.randint(0, 59), random.randint(0, 59))\n",
    "\n",
    "# 构造完整的时间对象，包含日期和时间\n",
    "random_datetime = datetime.datetime.combine(now.date(), random_time)\n",
    "\n",
    "print(\"随机时间对象是：\", random_datetime)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 生成车辆数据"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 模拟生成新能源车辆数据\n",
    "# 字段信息必须包含：车架号、行驶总里程、车速、车辆状态、充电状态、剩余电量SOC、SOC低报警、数据生成时间等\n",
    "def get_random_date(start:int,end:int) -> datetime:\n",
    "    '''\n",
    "    用来生成当天的随机时间\n",
    "\n",
    "    param:start 起始时间(小时)\n",
    "    param:end   结束时间(小时)\n",
    "    return:     当天的随机时间\n",
    "    '''\n",
    "    # 获取当前日期\n",
    "    now = datetime.datetime.now()\n",
    "\n",
    "    # 生成随机的小时数、分钟数和秒数，并构造时间对象\n",
    "    random_time = datetime.time(random.randint(start, end), random.randint(0, 59), random.randint(0, 59))\n",
    "\n",
    "    # 构造完整的时间对象，包含日期和时间\n",
    "    random_datetime = datetime.datetime.combine(now.date(), random_time)\n",
    "    \n",
    "    return random_datetime\n",
    "\n",
    "\n",
    "def generate_vehicle_data(vins:list,generate_time:datetime) -> dict:\n",
    "\n",
    "    '''\n",
    "    模拟生成新能源车辆数据\n",
    "    字段信息必须包含:车架号、行驶总里程、车速、车辆状态、充电状态、剩余电量SOC、SOC低报警、数据生成时间等\n",
    "\n",
    "    param:vins   一个存有车架号数据的列表\n",
    "    param:generate_time 数据生成时间\n",
    "    return: 新能源车辆数据\n",
    "    '''\n",
    "\n",
    "    vin = random.choice(vins)      # 车架号\n",
    "    # random.uniform用于生成指定范围内的随机浮点数。\n",
    "    mileage = round(random.uniform(200, 50000), 2)                                      # 行驶总里程\n",
    "    # 需要设置一下被选中的权重\n",
    "    status = random.choices(['行驶中', '停车中', '故障'],weights=[0.55,0.4,0.05])[0]      # 车辆状态\n",
    "    # 需要考虑车辆状态在什么情况下，它对应的行为\n",
    "    if (status == '行驶中'):\n",
    "        speed = round(random.uniform(0, 130), 2)            # 车速(考虑到有些人可能会超速行驶,所以最高车速为)\n",
    "        charging = '未充电'\n",
    "    else:\n",
    "        speed = 0\n",
    "        charging = random.choices(['未充电', '正在充电'],weights=[0.6,0.4])[0]     # 充电状态\n",
    "    \n",
    "    soc = round(random.uniform(10, 100), 2)                             # 剩余电量SOC\n",
    "    soc_low_alarm = '是' if soc < 30 else '否'                          # SOC低报警\n",
    "    generate_time = generate_time.strftime('%Y-%m-%d %H:%M:%S')         # 数据生成时间 \n",
    "\n",
    "    data = {\n",
    "        '车架号': vin,\n",
    "        '行驶总里程': mileage,\n",
    "        '车速': speed,\n",
    "        '车辆状态': status,\n",
    "        '充电状态': charging,\n",
    "        '剩余电量SOC': soc,\n",
    "        'SOC低报警': soc_low_alarm,\n",
    "        '数据生成时间': generate_time\n",
    "    }\n",
    "\n",
    "    return data\n",
    "    \n",
    "\n",
    "\n",
    "# 随机添加(20-60)重复数据\n",
    "def add_repeat_data(vehicle_data:list):\n",
    "    '''\n",
    "    随机添加(20-60)重复数据\n",
    "\n",
    "    param:vehicle_data  需要重复元素的列表\n",
    "    return:添加了重复元素后的列表\n",
    "    '''\n",
    "    # sample()函数从0到列表总长度之间随机选择random.randint(20,100)整数作为重复的数据下标\n",
    "    repeat_indices = random.sample(range(len(vehicle_data)), random.randint(20,100))\n",
    "    for i in repeat_indices:\n",
    "        # 返回字典中所有键值对的元素，以便同时访问字典中所有键和它们对应的值\n",
    "        data = {k:v for k,v in vehicle_data[i].items()}    \n",
    "\n",
    "        # 利用datetime.timedelta创建一段2秒间隔，然后将其加原时间上\n",
    "        data['数据生成时间'] = (datetime.datetime.strptime(data['数据生成时间'], '%Y-%m-%d %H:%M:%S') + \\\n",
    "            datetime.timedelta(seconds=2)).strftime('%Y-%m-%d %H:%M:%S')    \n",
    "\n",
    "        vehicle_data.insert(i+1, data)  # 将数据插入原数据的下一行\n",
    "        # print(vehicle_data[i])\n",
    "        # print(vehicle_data[i+1])\n",
    "    \n",
    "    return vehicle_data\n",
    "\n",
    "\n",
    "def get_other_day_data(vehicle_data:list, vins:list, ):\n",
    "    '''\n",
    "    混如少量前几天的数据(即数据生成时间不是当天，而是前几天的)\n",
    "    \n",
    "    param:vehicle_data  完整得车辆数据列表\n",
    "    param:vins          车架号\n",
    "    '''\n",
    "    repeat_indices = random.sample(range(len(vehicle_data)), random.randint(20,50)) # 随机获取20-50个数据容器长度的索引\n",
    "\n",
    "    for i in repeat_indices:\n",
    "        # 获取当天时间, 侧重于生成8-11点的数据，这样更能模拟白天开车的人居多\n",
    "        now_date = random.choices([get_random_date(0,23),get_random_date(8,11)],\\\n",
    "            weights=[0.4,0.6])[0] \n",
    "\n",
    "        # 利用datetime.timedelta创建一段随机1-3天的间隔，然后将减去原时间\n",
    "        time_delta = datetime.timedelta(days=random.randint(1,3))\n",
    "        # 获得前几天得时间\n",
    "        other_day = now_date - time_delta\n",
    "        \n",
    "        # 生成一条数据生成时间不是当天得车辆数据\n",
    "        data = generate_vehicle_data(vins,other_day)\n",
    "\n",
    "        vehicle_data.insert(i, data) # 随机插入到容器\n",
    "        # print(data)\n",
    "    \n",
    "    return vehicle_data"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 测试\n",
    "- 为了模拟车不是每天都开的情况，所以将车架号分开存，通过随机选取其中的2个空间车架号，就可以了了"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "import pandas as pd\n",
    "\n",
    "vins1 = pd.read_csv(\"vins1.csv\")['vin'].tolist()\n",
    "vins2 = pd.read_csv(\"vins2.csv\")['vin'].tolist()\n",
    "vins3 = pd.read_csv(\"vins3.csv\")['vin'].tolist()\n",
    "\n",
    "# 随机选取两个列表，然后将二维列表转化为一维列表\n",
    "random_vins = random.choices([vins1,vins2,vins3] , k=2)\n",
    "vins = random_vins[0]+random_vins[1]    # 车架号\n",
    "\n",
    "vehicle_data = []\n",
    "\n",
    "# 生成1000条数据\n",
    "for i in range(1000):\n",
    "    # 侧重于生成8-11点的数据，这样更能模拟白天开车的人居多\n",
    "    generate_time = random.choices([get_random_date(0,23),get_random_date(8,11)],weights=[0.4,0.6])[0]\n",
    "\n",
    "    data = generate_vehicle_data(vins,generate_time)\n",
    "    vehicle_data.append(data)   \n",
    "\n",
    "# 随机添加(20-100)重复数据\n",
    "vehicle_data = add_repeat_data(vehicle_data)\n",
    "\n",
    "# 混如20-50个前几天的数据即数据生成时间不是当天，而是前几天的\n",
    "vehicle_data = get_other_day_data(vehicle_data,vins)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 将文件存储"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "import random\n",
    "import datetime\n",
    "import json\n",
    "import os\n",
    "\n",
    "def ensure_file_exists(file_path):\n",
    "    '''\n",
    "    创建文件夹和检查文件是否存在\n",
    "\n",
    "    param:file_path 需要创建的文件路径\n",
    "    '''\n",
    "    if not os.path.exists(os.path.dirname(file_path)):\n",
    "        os.makedirs(os.path.dirname(file_path))\n",
    "\n",
    "    if not os.path.exists(file_path):\n",
    "        with open(file_path, 'w') as f:\n",
    "            f.write('')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "ename": "NameError",
     "evalue": "name 'datetime' is not defined",
     "output_type": "error",
     "traceback": [
      "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[1;31mNameError\u001b[0m                                 Traceback (most recent call last)",
      "\u001b[1;32m~\\AppData\\Local\\Temp\\ipykernel_16768\\2216586750.py\u001b[0m in \u001b[0;36m<module>\u001b[1;34m\u001b[0m\n\u001b[0;32m      3\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m      4\u001b[0m \u001b[1;31m# 获取当前日期\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 5\u001b[1;33m \u001b[0mnow\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mdatetime\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mdatetime\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mnow\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m      6\u001b[0m \u001b[1;31m# now = datetime.datetime.now() - datetime.timedelta(days=6)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m      7\u001b[0m \u001b[0mdate_str\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mnow\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mdate\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mstrftime\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m'%Y-%m-%d'\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n",
      "\u001b[1;31mNameError\u001b[0m: name 'datetime' is not defined"
     ]
    }
   ],
   "source": [
    "# 设置数据存储的路径\n",
    "data_path = '/can_data'\n",
    "\n",
    "# 获取当前日期\n",
    "now = datetime.datetime.now()\n",
    "# now = datetime.datetime.now() - datetime.timedelta(days=6)\n",
    "date_str = now.date().strftime('%Y-%m-%d')\n",
    "\n",
    "# 创建文件，并检查文件是否存在(存在则不创建)\n",
    "file_path = os.path.join(data_path, f'{date_str}/can-{date_str}.json')\n",
    "ensure_file_exists(file_path)\n",
    "\n",
    "# 将数据写入json文件中\n",
    "with open(file_path,'w',encoding='utf-8') as f:\n",
    "    # 将数据转化为json格式\n",
    "    json_data = json.dumps(vehicle_data,ensure_ascii=False) # ensvre_ascii=False展示中文"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [],
   "source": [
    "import json\n",
    "import os\n",
    "\n",
    "class JsonWriter(object):\n",
    "    \n",
    "    def __init__(self, file_path):\n",
    "        self.file_dir, self.file_name = os.path.split(file_path)\n",
    "        self.threshold = 100 * 1024 * 1024  # 文件大小阈值为100MB\n",
    "        self.current_size = 0  # 当前文件大小为0\n",
    "        self.file_index = 0  # 文件编号为0\n",
    "        self.file = None  # 文件句柄，初始为None\n",
    "    \n",
    "    def write(self, data):\n",
    "        # 计算数据的大小\n",
    "        encoded_data = json.dumps(data)\n",
    "        data_size = len(encoded_data.encode('utf-8'))\n",
    "        # 检查文件大小是否超过阈值\n",
    "        if self.file is None or self.current_size + data_size > self.threshold:\n",
    "            self.close()\n",
    "            self.open()\n",
    "        # 将数据写入文件中\n",
    "        self.file.write(encoded_data)\n",
    "        self.current_size += data_size\n",
    "        self.file.write('\\n')\n",
    "\n",
    "    def open(self):\n",
    "        # 创建新文件\n",
    "        index = self.file_index + 1\n",
    "        new_file_name = self.file_name\n",
    "        while os.path.exists(os.path.join(self.file_dir, new_file_name)):\n",
    "            index += 1\n",
    "            new_file_name = f\"{self.file_name}-{index}\"\n",
    "        self.file_name = new_file_name\n",
    "\n",
    "        self.file_index = index\n",
    "        self.file = open(os.path.join(self.file_dir, self.file_name), 'w')\n",
    "        self.current_size = 0\n",
    "\n",
    "    def close(self):\n",
    "        # 关闭当前文件\n",
    "        if self.file is not None:\n",
    "            self.file.close()\n",
    "            self.file = None\n",
    "    \n",
    "    def __del__(self):\n",
    "        # 析构函数，确保文件被关闭\n",
    "        self.close()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [],
   "source": [
    "writer = JsonWriter(\"/data.json\")\n",
    "data = {\"foo\": 1, \"bar\": 2, \"baz\": 3}\n",
    "for i in range(1000000):\n",
    "    writer.write(data)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3.9.13 ('base')",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.13"
  },
  "orig_nbformat": 4,
  "vscode": {
   "interpreter": {
    "hash": "79c2bc36f36ebca92fe5f3c73355a3866f3a34a4fd0b1d43087d5313dd5fd5ff"
   }
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
